// 节流
function throttle(time, cb) {
  let timer;
  return function (...args) {
    if (timer) {
      return;
    }
    timer = setTimeout(function () {
      timer = null;
      cb(...args);
    }, time);
  };
}
// 判断是否是滚动元素
const isScroll = (el, vertical) => {
  const determinedDirection = vertical !== null && vertical !== undefined;
  let overflow;
  if (determinedDirection) {
    if (vertical) {
      overflow = getStyleComputedProperty(el, 'overflow-y');
    } else {
      overflow = getStyleComputedProperty(el, 'overflow-x');
    }
  } else {
    overflow = getStyleComputedProperty(el, 'overflow');
  }
  return (
    overflow.match(/(scroll|auto|overlay)/) ||
    el.clientHeight < el.scrollHeight ||
    el.clientWidth < el.scrollWidth
  );
};
// 获取滚动容器
const getScrollContainer = (el, vertical) => {
  let parent = el;
  while (parent) {
    if ([window, document, document.documentElement].includes(parent)) {
      return parent;
    }
    if (isScroll(parent, vertical)) {
      return parent;
    }
    parent = parent.parentNode;
  }

  return parent;
};
// 是否是dom元素
function isHtmlElement(node) {
  return node && node.nodeType === window.Node.ELEMENT_NODE;
}
// 是否是undefined
function isUndefined(val) {
  return val === void 0;
}
// 是否是已定义
function isDefined(val) {
  return val !== undefined && val !== null;
}
const getStyleComputedProperty = (element, property) => {
  if (element === window) {
    element = document.documentElement;
  }

  if (element.nodeType !== 1) {
    return [];
  }
  // NOTE: 1 DOM access here
  const css = window.getComputedStyle(element, null);
  return property ? css[property] : css;
};

const entries = obj => Object.keys(obj || {}).map(key => [key, obj[key]]);

const getPositionSize = (el, prop) =>
  el === window || el === document ? document.documentElement[prop] : el[prop];

const getOffsetHeight = el => getPositionSize(el, 'offsetHeight');

const getClientHeight = el => getPositionSize(el, 'clientHeight');

const scope = 'InfiniteScroll';
const attributes = {
  delay: {
    type: Number,
    default: 200,
  },
  distance: {
    type: Number,
    default: 10,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  immediate: {
    type: Boolean,
    default: true,
  },
};

const getScrollOptions = (el, vm) => {
  if (!isHtmlElement(el)) {
    return {};
  }

  return entries(attributes).reduce((map, [key, option]) => {
    const { type, default: defaultValue } = option;
    let value = el.getAttribute(`infinite-scroll-${key}`) || void 0;
    switch (type) {
      case Number:
        value = Number(value);
        value = Number.isNaN(value) ? defaultValue : value;
        break;
      case Boolean:
        if (isDefined(value)) {
          if (value === 'false') {
            value = false;
          } else {
            value = Boolean(value);
          }
        } else {
          value = defaultValue;
        }
        break;
      default:
        value = type(value);
    }
    map[key] = value;
    return map;
  }, {});
};

const getElementTop = el => el.getBoundingClientRect().top;

const handleScroll = function (cb) {
  const { el, vm, container, observer } = this[scope];
  const { distance, disabled } = getScrollOptions(el, vm);
  if (disabled) {
    return;
  }
  const containerInfo = container.getBoundingClientRect();
  if (!containerInfo.width && !containerInfo.height) {
    return;
  }
  let shouldTrigger = false;
  if (container === el) {
    // be aware of difference between clientHeight & offsetHeight & window.getComputedStyle().height
    const scrollBottom = container.scrollTop + getClientHeight(container);
    shouldTrigger = container.scrollHeight - scrollBottom <= distance;
  } else {
    const heightBelowTop = getOffsetHeight(el) + getElementTop(el) - getElementTop(container);
    const offsetHeight = getOffsetHeight(container);
    const borderBottom = Number.parseFloat(
      getStyleComputedProperty(container, 'borderBottomWidth')
    );
    shouldTrigger = heightBelowTop - offsetHeight + borderBottom <= distance;
  }
  if (shouldTrigger && typeof cb === 'function') {
    cb.call(vm);
  } else if (observer) {
    observer.disconnect();
    this[scope].observer = null;
  } else {
    // 解决sonar问题
  }
};

export default {
  inserted(el, binding, vnode) {
    const cb = binding.value;
    const vm = vnode.context;
    // only include vertical scroll
    const container = getScrollContainer(el, true);
    const { delay, immediate } = getScrollOptions(el, vm);
    const onScroll = throttle(delay, handleScroll.bind(el, cb));

    el[scope] = { el, vm, container, onScroll };
    if (container) {
      container.addEventListener('scroll', onScroll);
      if (immediate) {
        const observer = (el[scope].observer = new MutationObserver(onScroll));
        observer.observe(container, { childList: true, subtree: true });
        onScroll();
      }
    }
  },
  unbind(el) {
    const { container, onScroll } = el[scope];
    if (container) {
      container.removeEventListener('scroll', onScroll);
    }
  },
};
